/* 
 * NeXTSTEP 3.0 mods - This was simply rebuilt with Project Builder.
 * Of course, there is always the new 3.0 Help system, but if you want
 * to try this simple approach, it still works.
 *
 *
 * Author: Julie Zelenski, NeXT Developer Support
 * (This is a simpler, reusable version of the one by Julie in
 * NextDeveloper/Examples/BusyBox.  This one leaves out the context-
 * sensitive help.  --  John Glover, University of Houston, glover@uh.edu)
 *
 * You may freely copy, distribute and reuse the code in this example.  
 * NeXT disclaims any warranty of any kind, expressed or implied, as to 
 * its fitness for any particular use.
 *
 * Help.m, a help object to manage and display RTF help files.
 * The help object owns its own nib section "Help.nib" which has a
 * Help panel - with an NXBrowser to display the help topics, and
 * a scrolling text view to display the help files.  The help files are
 * all stored as RTF text files in a directory called Help within the
 * app wrapper.  At init time, the Help object loads the browser with 
 * names of all the files found in the Help directory.  When a name is
 * chosen from the browser, the help object opens a stream on that file
 * and read the rich text into the text object.   This object is a useful 
 * addition to any program, because all users appreciate help!
 *
 * To use this:
 *    1.  Copy  Help.m, Help.h, and Help.nib to your project directory and add
 *        them to the project.
 *    2.  In the main .nib file, create Help as a new subclass of Object and parse it.
 *    3.  Instantiate a Help object in the main .nib file.
 *    4.  Connect your Help... menu item to the generalHelp: method of the
 *        help instance.
 *    5.  Connect your Print... menu item to the print: method of the
 *        help instance.
 *    6.  Modify the text at the top of the Help.nib panel to suit your application.
 *    7.  Build your application as an .app, and create a Help folder within the .app folder.
 *        In that folder, all .rtf filenames will show up as help topics.
 *
 */
 
// #import  <stdio.h>
#import "Help.h"
/* #import <appkit/Button.h>
#import <appkit/Cell.h>
#import <appkit/Matrix.h>
#import <appkit/MenuCell.h>
#import <appkit/NXBrowser.h>
#import <appkit/NXBrowserCell.h>
#import <appkit/ScrollView.h> 
#import <appkit/appkit.h>*/
//#import <dpsclient/wraps.h>
#import <sys/dir.h> //for getdirentries()
//#import <libc.h>     
//#import  <appkit/Application.h>
//#import  <appkit/defaults.h>  // for NXArgv

@implementation Help:Object


- init 
/* For newly created help object, loads the nib section with the Help
 * panel which has the topics browser and a scrolling text view for 
 * displaying help files.  Gets the appDirectory from NXApp, finds help 
 * directory in app wrapper.
 */
{
    sprintf(helpDirectory,"%s/%s",[self appDirectory],"Help");
     helpPanel = [NXApp loadNibSection:"Help.nib" owner:self];
    return self;
}

/* Copied this from BusyBoxApp  with  minor changes.
 * I have combined the two methods used there (new and 
 * appDirectory) into one.
 */
- (const char *)appDirectory
{
	char *suffix;
	
	strcpy(appDirectory, NXArgv[0]);
	if (suffix = rindex(appDirectory,'/'))
		*suffix = '\0';
	if (appDirectory) chdir(appDirectory);
	getwd(appDirectory);
	return  appDirectory;
}
	
- setHelpBrowser:anObject;
/* Sets the helpBrowser outlet, and calls on the browser to load up.
 */
{
    helpBrowser = anObject;
    [helpBrowser setDelegate:self];
    [helpBrowser loadColumnZero];
    return self;
}

/* TARGET/ACTION METHODS */

- generalHelp:sender;
/* This is the target/action method for the "Help" menu item.  This method 
 * will show the "general help" file.
 */
{
    [self showHelpFile:"General Help"];
    return self;
}

- browserHit:sender
/* This is the target/action method from the help topics browser.  When
 * a help topic is selected, this method will show the help file for that
 * topic.
 */
{   
    [self showHelpFile:[[[sender matrixInColumn:0] selectedCell] stringValue]];
    return self;
}


- print:sender;
/* This method is called by the Print menu cell in the main menu.  It will 
 * print the current help file.
 */
{
    [[helpScrollView docView] printPSCode:sender];
    return self;
}


/* HELP METHODS */

- showHelpFile:(const char*)filename;
/* Tries to open a stream for the specified RTF text file in the Help 
 * directory so the text object can readRichText.  Also selects the
 * filename in the browser of help topics. It also brings the
 * help panel to the front.
 */
{
   NXStream *stream;
   char helpFile[MAXPATHLEN];
   static NXPoint origin = {0.0,0.0};

    [self browser:helpBrowser selectCell:filename inColumn:0];
    sprintf(helpFile,"%s/%s.rtf",helpDirectory,filename);
    stream = NXMapFile(helpFile,NX_READONLY);
    if (stream != NULL) {
    	[helpPanel disableFlushWindow];
        [[helpScrollView docView] readRichText:stream]; 
	[[helpScrollView docView] scrollPoint:&origin];
	[[helpPanel reenableFlushWindow] flushWindow];
    	NXCloseMemory(stream,NX_FREEBUFFER);
    }
    [helpPanel orderFront:self];
    return self;
}


/* BROWSER DELEGATE METHODS */


#define CHUNK 127
static char **addFile(const char *file, int length, char **list, int count)
/* Adds the specified filename to the list of filenames.  It allocates 
 * more memory in chunks as needed.
 */
{
    char *suffix;
    
    if (!list) list = (char **)malloc(CHUNK*sizeof(char *));
    if (suffix = rindex(file,'.')) 
        *suffix  = '\0'; 	/* strip rtf suffix */
    list[count] = (char *)malloc((length+1)*sizeof(char));
    strcpy(list[count], file);
    count++;
    if (!(count% CHUNK)) {
	list = (char **)realloc(list,(((count/CHUNK)+1)*CHUNK)*sizeof(char *));
    }
    list[count] = NULL;
    return list;
}

static void freeList(char **list)
/* Frees the array of filenames
 */
 {
    char **strings;

    if (list) {
	strings = list;
	while (*strings) free(*strings++);
	free(list);
    }
}

static BOOL isOk(const char *s)
/* checks to make sure the filename is not NULL and to verify that it is
 * not a "dot"--hidden file.
 */
{
    return (!s[0] || s[0] == '.') ? NO : YES;
}

static int caseInsensitiveCompare(void *arg1, void *arg2)
/* Compares the two arguments without regard for case using strcasecmp().
*/
{
    char *string1, *string2;

    string1 = *((char **)arg1);
    string2 = *((char **)arg2);
    return strcasecmp(string1,string2);
}

static char **fileList;

- (int)browser:sender fillMatrix:matrix inColumn:(int)column
/* This delegate method goes out to the help directory and gets a list
 * of all the files in that directory.  It creates a list of file names
 * for the static variable fileList, and will load the filenames into the 
 * browser on demand (lazy loading).
 */
{
    long basep;
    char *buf;
    struct direct *dp;
    char **list = NULL;
    int cc, fd, fileCount = 0;
    char dirbuf[8192];

    if ((fd = open(helpDirectory, O_RDONLY, 0644)) > 0) {
	cc = getdirentries(fd, (buf = dirbuf), 8192, &basep);
	while (cc) {
	    dp = (struct direct *)buf;
	    if (isOk(dp->d_name)) {
		list = addFile(dp->d_name, dp->d_namlen, list, fileCount++);
	    }
	    buf += dp->d_reclen;
	    if (buf >= dirbuf + cc) {
		cc = getdirentries(fd, (buf = dirbuf), 8192, &basep);
	    }
	}
	close(fd);
	if (list) qsort(list,fileCount,sizeof(char *),caseInsensitiveCompare);
    }
    freeList(fileList);
    fileList = list;
    return fileCount;
}

- browser:sender loadCell:cell atRow:(int)row inColumn:(int)column
/* This delegate method loads the cell for a given row.  The stringValue
 * for that row comes from the fileList.
 */
{
    if (fileList) {
	[cell setStringValueNoCopy:fileList[row]];
	[cell setLeaf:YES];
    }
    return self;
}


- (BOOL)browser:sender selectCell:(const char *)title inColumn:(int)column
/* This delegate method selects the cell with the given title.  If it finds
 * a cell with that title, it verifies that it has a file entry in the 
 * fileList, forces the loading of the cell, selects it (highlights) and
 * scrolls the browser so the cell is visible.  It returns a boolean value
 * which indicates whether the cell was found.
 */
{
    int row;
    id matrix;

    if (title) {
	matrix = [sender matrixInColumn:column];
	if (!fileList) return NO;
	for (row = [matrix cellCount]-1; row >= 0; row--) {
	    if (fileList[row] && !strcmp(title, fileList[row])) {
		[sender getLoadedCellAtRow:row inColumn:column];
		[matrix selectCellAt:row :0];
		[matrix scrollCellToVisible:row :0];
		return YES;
	    }
	}
    }
    return NO;
}


/* WINDOW DELEGATE METHODS */

- windowWillResize:sender toSize:(NXSize *)frameSize;
/* This method constrains the Help Panel to a reasonable minimum size
 * when the user resizes the panel.
 */
{
    frameSize->width = MAX(frameSize->width,400.0);
    frameSize->height = MAX(frameSize->height,350.0);
    return self;
}


@end